﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace G.Animation.PathAnimation
{
    /// <summary>
    /// pathAnimation基类，参考地址：http://www.codeproject.com/Articles/30819/Animation-Along-a-Path-for-Silverlight
    /// </summary>
    public abstract class BaseAnimationUsingPath : DependencyObject
    {
        #region BeginTime DP
        //
        // Summary:
        //     Gets or sets the time at which this System.Windows.Media.Animation.Timeline
        //     should begin.
        //
        // Returns:
        //     The start time of the time line. The default value is zero.
        public TimeSpan BeginTime
        {
            get { return (TimeSpan)GetValue(BeginTimeProperty); }
            set { SetValue(BeginTimeProperty, value); }
        }

        public static readonly DependencyProperty BeginTimeProperty =
            DependencyProperty.Register("BeginTime", typeof(TimeSpan), typeof(BaseAnimationUsingPath),
            new PropertyMetadata((TimeSpan)TimeSpan.Zero,
                new PropertyChangedCallback(OnPropertyChanged)));
        #endregion

        #region Duration DP
        //
        // Summary:
        //     Gets or sets the length of time for which this timeline plays.
        public TimeSpan Duration
        {
            get { return (TimeSpan)GetValue(DurationProperty); }
            set { SetValue(DurationProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Duration.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DurationProperty =
            DependencyProperty.Register("Duration", typeof(TimeSpan), typeof(BaseAnimationUsingPath),
            new PropertyMetadata(TimeSpan.FromSeconds(1.0),
               new PropertyChangedCallback(OnPropertyChanged)));

        #endregion

        #region AutoReverse DP
        // Summary:
        //     Gets or sets a value that indicates whether the timeline plays in reverse
        //     after it completes a forward iteration.
        //
        // Returns:
        //     true if the animation plays in reverse at the end of each iteration; otherwise,
        //     false. The default value is false.
        public bool AutoReverse
        {
            get { return (bool)GetValue(AutoReverseProperty); }
            set { SetValue(AutoReverseProperty, value); }
        }

        public static readonly DependencyProperty AutoReverseProperty =
            DependencyProperty.Register("AutoReverse", typeof(bool), typeof(BaseAnimationUsingPath),
            new PropertyMetadata((bool)false,
               new PropertyChangedCallback(OnAutoReversePropertyChanged)));

        static void OnAutoReversePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            BaseAnimationUsingPath anim = d as BaseAnimationUsingPath;

            if (anim == null)
                return;

            if (e.NewValue == e.OldValue)
                return;

            if (anim.Story != null)
                anim.Story.AutoReverse = (bool)e.NewValue;
        }

        #endregion

        #region FillBehavior DP
        //
        // Summary:
        //     Gets or sets a value that specifies how the animation behaves after it reaches
        //     the end of its active period.
        //
        // Returns:
        //     A value that specifies how the animation behaves after it reaches the end
        //     of its active period but its parent is inside its active or fill period.
        //     The default value is System.Windows.Media.Animation.FillBehavior.HoldEnd.
        public FillBehavior FillBehavior
        {
            get { return (FillBehavior)GetValue(FillBehaviorProperty); }
            set { SetValue(FillBehaviorProperty, value); }
        }

        public static readonly DependencyProperty FillBehaviorProperty =
            DependencyProperty.Register("FillBehavior", typeof(FillBehavior), typeof(BaseAnimationUsingPath),
            new PropertyMetadata((FillBehavior)FillBehavior.HoldEnd,
               new PropertyChangedCallback(OnFillBehaviorPropertyChanged)));

        static void OnFillBehaviorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            BaseAnimationUsingPath anim = d as BaseAnimationUsingPath;

            if (anim == null)
                return;

            if (e.NewValue == e.OldValue)
                return;

            if (anim.Story != null)
                anim.Story.FillBehavior = (FillBehavior)e.NewValue;
        }

        #endregion

        #region RepeatBehavior DP
        //
        // Summary:
        //     Gets or sets the repeating behavior of this animation.
        //
        // Returns:
        //     An iteration System.Windows.Media.Animation.RepeatBehavior.Count that specifies
        //     the number of times the animation should play, a System.TimeSpan value that
        //     specifies the total length of this animation's active period, or the special
        //     value System.Windows.Media.Animation.RepeatBehavior.Forever, which specifies
        //     that the timeline should repeat indefinitely. The default value is a System.Windows.Media.Animation.RepeatBehavior
        //     with a System.Windows.Media.Animation.RepeatBehavior.Count of 1, which indicates
        //     that the animation plays once.
        public RepeatBehavior RepeatBehavior
        {
            get { return (RepeatBehavior)GetValue(RepeatBehaviorProperty); }
            set { SetValue(RepeatBehaviorProperty, value); }
        }

        public static readonly DependencyProperty RepeatBehaviorProperty =
            DependencyProperty.Register("RepeatBehavior", typeof(RepeatBehavior), typeof(BaseAnimationUsingPath),
            new PropertyMetadata((RepeatBehavior)new RepeatBehavior(1),
               new PropertyChangedCallback(OnRepeatBehaviorPropertyChanged)));

        static void OnRepeatBehaviorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            BaseAnimationUsingPath anim = d as BaseAnimationUsingPath;

            if (anim == null)
                return;

            if (e.NewValue == e.OldValue)
                return;

            if (anim.Story != null)
                anim.Story.RepeatBehavior = (RepeatBehavior)e.NewValue;
        }

        #endregion

        #region PathGeometry DP
        public Geometry PathGeometry
        {
            get { return (Geometry)GetValue(PathGeometryProperty); }
            set { SetValue(PathGeometryProperty, value); }
        }

        public static readonly DependencyProperty PathGeometryProperty =
            DependencyProperty.Register("PathGeometry", typeof(Geometry), typeof(BaseAnimationUsingPath),
            new PropertyMetadata(null,
                 new PropertyChangedCallback(OnPropertyChanged)));
        #endregion

        #region TargetProperty DP
        //
        // Summary:
        //     Makes the animation target the specified dependency property.
        // 
        public PropertyPath TargetProperty
        {
            get { return (PropertyPath)GetValue(TargetPropertyProperty); }
            set { SetValue(TargetPropertyProperty, value); }
        }

        public static readonly DependencyProperty TargetPropertyProperty =
            DependencyProperty.Register("TargetProperty", typeof(PropertyPath), typeof(BaseAnimationUsingPath),
            new PropertyMetadata(null,
                new PropertyChangedCallback(OnPropertyChanged)));

        #endregion

        #region Target DP
        //
        // Summary:
        //     Makes the animation target the specified dependency object.
        // 
        public DependencyObject Target
        {
            get { return (DependencyObject)GetValue(TargetPropertyDP); }
            set { SetValue(TargetPropertyDP, value); }
        }

        public static readonly DependencyProperty TargetPropertyDP =
            DependencyProperty.Register("Target", typeof(DependencyObject), typeof(BaseAnimationUsingPath),
            new PropertyMetadata(null,
                new PropertyChangedCallback(OnPropertyChanged)));
        #endregion

        #region Tolerance DP
        //
        // Summary:
        //     Controls the number of line segments used to approximate the path geometry
        //     Must be greater than 0.  Larger tolerance value uses fewer segments.  
        //     Default is 10.
        // 
        public double Tolerance
        {
            get { return (double)GetValue(ToleranceProperty); }
            set { SetValue(ToleranceProperty, value); }
        }

        public static readonly DependencyProperty ToleranceProperty =
            DependencyProperty.Register("Tolerance", typeof(double), typeof(BaseAnimationUsingPath),
            new PropertyMetadata((double)10.0,
                new PropertyChangedCallback(OnTolerancePropertyChanged)));

        static void OnTolerancePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            BaseAnimationUsingPath anim = d as BaseAnimationUsingPath;

            if (anim == null)
                return;

            if (e.NewValue == e.OldValue)
                return;

            double value = (double)e.NewValue;

            if (value <= 0.0)
                anim.Tolerance = 10.0;

            anim.IsStoryDirty = true;
        }
        #endregion

        static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            BaseAnimationUsingPath anim = d as BaseAnimationUsingPath;

            if (anim == null)
                return;

            if (e.NewValue == e.OldValue)
                return;

            // either Target, TargetProperty, Duration, or BeginTime property
            // has changed, so indicate that the animation is dirty (i.e., we will need
            // to rebuild the storyboard the next time 'begin' is called.  This means, 
            // that if an animation is currently in progress, then changing any of 
            // these values will not take effect until the next time 'begin' is called.
            // This avoid unncessary overhead of rebuilding storyboard everytime 'begin'
            // is called.
            anim.IsStoryDirty = true;
        }

        Storyboard _story;
        internal Storyboard Story
        {
            get { return _story; }
            set { _story = value; }
        }

        bool _isStoryDirty = true;
        internal bool IsStoryDirty
        {
            get { return _isStoryDirty; }
            set { _isStoryDirty = value; }
        }

        public BaseAnimationUsingPath()
        {
        }

        abstract public void CreateStory();

        public void Begin()
        {
            CreateStory();

            if (Story != null)
                Story.Begin();
        }

        public void Pause()
        {
            if (Story != null)
                Story.Pause();
        }

        public void Stop()
        {
            if (Story != null)
                Story.Stop();
        }

        public void Resume()
        {
            if (Story != null)
                Story.Resume();
        }

        public void Seek(TimeSpan Time)
        {
            if (Story != null)
                Story.Seek(Time);
        }

        public event EventHandler Completed;

        protected virtual void OnCompleted(EventArgs e)
        {
            if (Completed != null)
                Completed(this, e);
        }

        internal List<Point> GetPointsOnFlattenedPath(PathGeometry FlattenedPath, out double Length)
        {
            List<Point> flattenedPathPoints = new List<Point>();

            Length = 0;

            // for flattened geometry there should be just one PathFigure in the Figures
            if (FlattenedPath.Figures.Count != 1)
                return null;

            PathFigure pathFigure = FlattenedPath.Figures[0];

            Point lastPoint = pathFigure.StartPoint;

            flattenedPathPoints.Add(pathFigure.StartPoint);

            // SegmentsCollection should contain PolyLineSegment and LineSegment
            foreach (PathSegment pathSegment in pathFigure.Segments)
            {
                if (pathSegment is PolyLineSegment)
                {
                    PolyLineSegment seg = pathSegment as PolyLineSegment;

                    foreach (Point point in seg.Points)
                    {
                        Length += CalculateDistance(lastPoint, point);
                        lastPoint = point;

                        flattenedPathPoints.Add(point);
                    }
                }
                else if (pathSegment is LineSegment)
                {
                    LineSegment seg = pathSegment as LineSegment;

                    Length += CalculateDistance(lastPoint, seg.Point);
                    lastPoint = seg.Point;

                    flattenedPathPoints.Add(seg.Point);
                }
                else
                    throw new Exception("GetIntersectionPoint - unexpected path segment type: " + pathSegment.ToString());

            }

            return (flattenedPathPoints);
        }

        internal double CalculateDistance(Point A, Point B)
        {
            return Math.Sqrt(Math.Pow(A.X - B.X, 2.0) + Math.Pow(A.Y - B.Y, 2.0));
        }
    }
}
